<?php

namespace Core\Common\Route;

use Core\Common\Base\Config;
use Core\Common\Util\DocParser;

class Dispatcher
{

    const DEFAULT_ACTION = 'run';

    private $basePath = '';
    private static $instance = null;
    private $cache = [];
    private $register = [];
    private $routerMiddleware = [];
    private $routePrefix = '';
    private $routePart = 3;
    private $routeMiddlewareDocComment = "";

    private function __construct($basepath)
    {
        if ('' == $basepath) {
            if (defined('CORE_WORK_ROOT')) {
                $this->basePath = CORE_WORK_ROOT;
            } else {
                $this->basePath = dirname(dirname(dirname(dirname(dirname(__DIR__))))) . DIRECTORY_SEPARATOR;
            }
        } else {
            $this->basePath = $basepath;
        }
        $routePrefix = trim(Config::config('route.prefix'), '/') ?? '';
        $this->routeMiddlewareDocComment = Config::config('route.middleware') ?? 'sqkb-middleware';
        if ($routePrefix) {
            $this->routePrefix = $routePrefix . '/';
            $this->routePart += count(explode('/', $routePrefix));
        }
    }

    /**
     * @return \self
     */
    public static function getInstance($basepath = '')
    {
        if (is_null(self::$instance)) {
            self::$instance = new self($basepath);
        }
        return self::$instance;
    }

    /**
     * 设置URI存储对象，仅在SuperServer中使用
     * @param \Swoole\Table $obj
     */
    public function setUriObj($obj)
    {
        if (empty($this->register)) {
            $this->register = $obj;
        }
    }

    /**
     * 判断是否存在某个URI处理类
     * @param string $uri
     * @return boolean
     */
    public function hasUriHandler($uri)
    {
        if (is_array($this->register)) {
            return isset($this->register[$uri]) ? true : false;
        }
        if (class_exists('\Swoole\Table') && $this->register instanceof \Swoole\Table) {
            $str = $this->register->get($uri);
            if ($str) {
                return true;
            }
        }
        return false;
    }

    /**
     * 获取某个URI处理类
     * @param string $uri
     * @return mixed
     */
    public function getUriHandler($uri)
    {
        if (is_array($this->register)) {
            return isset($this->register[$uri]) ? $this->register[$uri] : '';
        }
        if (class_exists('\Swoole\Table') && $this->register instanceof \Swoole\Table) {
            $arr = $this->register->get($uri);
            if ($arr) {
                return json_decode($arr['detail'], true);
            }
        }
        return null;
    }

    /**
     * 初始化，解析Controller目录下的有效的路由
     * @return array
     * @throws
     */
    public function initUri()
    {
        $handle = opendir(CORE_CONTROLLER_DIR);
        if (!$handle) {
            return [];
        }
        $this->register = [];
        $prefix = 'App\Controller\\';
        $tmp = array();
        while (false !== ($entry = readdir($handle))) {
            if ('.' == $entry || '..' == $entry) {
                continue;
            }
            if (is_file(CORE_CONTROLLER_DIR . $entry)) {
                $uri = strtolower(str_replace(['Controller', '.php'], ['', ''], $entry));
                $classname = $prefix . substr($entry, 0, -4);
                $tmp[$uri] = $classname;
                continue;
            }
            $files = scandir(CORE_CONTROLLER_DIR . $entry);
            foreach ($files as $file) {
                if ('.' == $file || '..' == $file) {
                    continue;
                }
                $uri = strtolower($entry . '/' . str_replace(['Controller', '.php'], ['', ''], $file));
                $classname = $prefix . $entry . '\\' . substr($file, 0, -4);
                $tmp[$uri] = $classname;
            }
        }
        closedir($handle);
        $docParser = new DocParser();
        foreach ($tmp as $uri => $classname) {
            if (!class_exists($classname)) {
                continue;
            }
            $reflection = new \ReflectionClass($classname);
            $methods = $reflection->getMethods(\ReflectionMethod::IS_PUBLIC);
            $middleWares = defined("$classname::MIDDLEWARE") ? $classname::MIDDLEWARE : [];
            $midArr = [];
            foreach ($middleWares as $midClass => $expected) {
                if (!class_exists($midClass)) {
                    continue;
                }
                if (!is_subclass_of($midClass, '\Core\Common\Base\Middleware')) {
                    continue;
                }
                $midArr[$midClass] = $expected;
            }
            foreach ($methods as $method) {
                $middlewareDoc = $docParser->getParam($method->getDocComment(), $this->routeMiddlewareDocComment) ?? "";
                $aliasMiddlewareList = explode(",", $middlewareDoc);

                if ($method->name == self::DEFAULT_ACTION) {
                    if ('index' == $uri) {
                        $uriAlias = $uri;
                    } else {
                        $uriAlias = $this->routePrefix . $uri;
                    }
                    $this->register[$uriAlias] = [
                        $classname,
                        $method->name,
                        "",
                        $this->convertRouterMiddlewareList($aliasMiddlewareList)
                    ];
                    continue;
                }
                if ('Action' != substr($method->name, -6)) {
                    continue;
                }

                $methodName = substr($method->name, 0, -6);
                $httpMethod = '';
                $newMethodName = $methodName;
                if (strlen($methodName) >= 4) {
                    $suffixName = substr($methodName, -4);
                    $reqSuffix = str_replace(['Get', 'Post', 'Put',], ['', '', '',], $suffixName);
                    if (strlen($reqSuffix) < strlen($methodName)) {
                        $tryMethodName = substr($methodName, 0, -4) . $reqSuffix;
                        if (strlen($tryMethodName)) {
                            $newMethodName = $tryMethodName;
                            $httpMethod = str_replace($newMethodName, '', $methodName);
                        }
                    }
                }
                $this->register[$this->routePrefix . $uri . '/' . strtolower($newMethodName)] = [$classname, $method->name, strtoupper($httpMethod), $this->convertRouterMiddlewareList($aliasMiddlewareList)];
            }
        }
        if (isset($this->register['index'])) {
            $this->register[''] = $this->register['index'];
        }
        if (class_exists('\App\Config\Common') && defined('\App\Config\Common::ROUTE_REWRITE')) {
            foreach (\App\Config\Common::ROUTE_REWRITE as $from => $to) {
                if (isset($this->register[trim($to, '/')])) {
                    $this->register[trim($from, '/')] = $this->register[trim($to, '/')];
                } else if (isset($this->register[$this->routePrefix . trim($to, '/')])) {
                    $this->register[trim($from, '/')] = $this->register[$this->routePrefix . trim($to, '/')];
                }
            }
        }
        return $this->register;
    }

    /**
     * 处理辅助工具URI
     * @param string $name
     * @param mixed $arr
     */
    public function special($name, $arr)
    {
        if ('product' == CORE_ENV && '127.0.0.1' != Request::server('REMOTE_ADDR')) {
            Response::error('request not allowed', 403);
            return;
        }
        array_shift($arr);
        $suffix = ucwords(str_replace('_', '\\', $name), '\\');
        $classname = '\App\Debug\\' . $suffix;
        if (!class_exists($classname)) {
            $classname = '\Core\Debug\\' . $suffix;
        }
        $classname::execute(...$arr);
    }

    private function getHandler(&$arr)
    {
        $className = null;
        $busi = ucfirst(array_shift($arr));
        if ('' == $busi) {
            $busi = 'Index';
        }
        $module = ucfirst(array_shift($arr));
        if ('' == $module) {
            $className = 'App\Controller\\' . $busi . 'Controller';
        } else if (is_dir($this->basePath . 'Controller' . DIRECTORY_SEPARATOR . $busi . DIRECTORY_SEPARATOR . $module)) {
            $action = lcfirst(array_shift($arr));
            $className = 'App\Controller\\' . $busi . '\\' . $module . '\\' . $action . 'Controller';
        } else {
            $className = 'App\Controller\\' . $busi . '\\' . $module . 'Controller';
        }
        if (!class_exists($className)) {
            Response::error('NO classname found: ' . $className, 404);
            return false;
        }
        $method = '';
        if (isset($arr[0])) {//method
            $prefix = lcfirst(ucwords($arr[0], '_'));
            $reqMethod = $prefix . ucfirst(Request::server('REQUEST_METHOD')) . 'Action';
            $AnyMethod = $prefix . 'Action';
            if (method_exists($className, $reqMethod)) {
                array_shift($arr);
                $method = $reqMethod;
            } else if (method_exists($className, $AnyMethod)) {
                array_shift($arr);
                $method = $AnyMethod;
            }
        }
        if ('' == $method) {
            $method = self::DEFAULT_ACTION;
            if (!method_exists($className, $method)) {
                Response::error('NO method found', 404);
                return false;
            }
        }
        return [$className, $method];
    }

    /**
     * 获取全部的路由信息
     * @return mixed
     */
    public function routes()
    {
        return $this->register;
    }

    /**
     * 获取所有路由url列表
     * @return array
     */
    public function getRouteUriList()
    {
        if (empty($this->register) || !is_array($this->register)) {
            return [];
        }
        return array_filter(array_keys($this->register));
    }

    /**
     * 处理有缓存记录的路由，Swoole环境下使用
     * @param mixed $uri
     * @param array $param
     * @return mixed
     */
    public function proceed($uri = '', $param = [])
    {
        if ('' == $uri) {
            $uri = trim(Request::server('REQUEST_URI'), '/');
        }
        $urlInfo = parse_url($uri);
        if (isset($urlInfo['query'])) {
            $queryArr = [];
            parse_str($urlInfo['query'], $queryArr);
            foreach ($queryArr as $k => $v) {
                Request::get($k, $v);
            }
        }
        $arr = explode('/', trim($urlInfo['path']));
        if (isset($arr[0][0]) && '_' == $arr[0][0]) {
            $this->special(substr($arr[0], 1), $arr);
            return;
        }
        if (empty($this->register)) {
            $this->initUri();
        }
        $pos = 0;
        $handler = $this->getUriHandler($uri);
        if (empty($handler)) {
            Response::error('no route found', 404);
            return;
        }
        list($className, $method, $reqMethod, $routeMiddlewareList) = $handler;
        if ($reqMethod && $reqMethod != Request::server('REQUEST_METHOD')) {
            Response::error("Method Not Allowed", 405);
            return;
        }
        while ($pos--) {
            array_shift($arr);
        }
        if ($param) {
            $arr = array_merge($arr, $param);
        }
        if (Request::type() == Request::HTTP) {
            if (defined("$className::MIDDLEWARE") && $className::MIDDLEWARE) {
                foreach ($className::MIDDLEWARE as $midClass => $expected) {
                    if (!class_exists($midClass)) {
                        Response::error("Middleware $midClass no found", 500);
                        return;
                    }
                    if (!is_subclass_of($midClass, 'Core\Common\Base\Middleware')) {
                        Response::error("Middleware $midClass did not implements Middleware interface", 500);
                        return;
                    }
                    $ret = $midClass::run();
                    if ($expected !== $ret) {
                        Response::error($midClass::error(), $midClass::errorCode(), $midClass);
                        return;
                    }
                }
            }

            if (!empty($routeMiddlewareList)) {
                foreach ($routeMiddlewareList as $middleware) {
                    if (!class_exists($middleware)) {
                        Response::error("Middleware $middleware no found", 500);
                        return;
                    }

                    $ret = $middleware::run();
                    if ($ret !== $middleware::$ret) {
                        Response::trigger($middleware::error());
                        return;
                    }
                }
            }
        }
        $className::$method(...$arr);
    }
    /*
     * helloworld.Greeter/SayHello
     */
    public function http2($uri)
    {
        list($tmp, $method0) = explode('/', $uri);
        $className = 'App\Controller\\' . ucfirst(str_replace(['com.sqkb.', '.'], ['', '\\'], $tmp)) . 'Controller';
        $method = $method0 . 'Action';
        $className::$method();
    }

    /**
     * 实时解析路由，以及分配处理类和方法, php-fpm
     * @return mixed
     */
    public function run($uri = '')
    {
        if (!empty($this->register)) {
            $this->proceed($uri);
            return;
        }
        if ('' == $uri) {
            $uri = trim(Request::server('REQUEST_URI'), '/');
        }
        $urlInfo = parse_url($uri);
        $uri = trim($urlInfo['path']);
        if (isset($urlInfo['query'])) {
            $queryArr = [];
            parse_str($urlInfo['query'], $queryArr);
            foreach ($queryArr as $k => $v) {
                Request::get($k, $v);
            }
        }
        $arr = explode('/', $uri);
        if (isset($arr[0][0]) && '_' == $arr[0][0]) {
            $this->special(substr($arr[0], 1), $arr);
            return;
        }
        if (isset($this->cache[$uri])) {
            $handler = $this->cache[$uri];
            $arr = [];
        } else {
            if (class_exists('\App\Config\Common') && defined('\App\Config\Common::ROUTE_REWRITE')) {
                $tryUri = '';
                foreach ($arr as $try) {
                    $tryUri .= $try;
                    if (isset(\App\Config\Common::ROUTE_REWRITE[$tryUri])) {
                        $arr = explode('/', \App\Config\Common::ROUTE_REWRITE[$tryUri]);
                        break;
                    }
                    $tryUri .= '/';
                }
            }
            $handler = $this->getHandler($arr);
            if (false === $handler) {
                return;
            }
            if (empty($arr)) {
                $this->cache[$uri] = $handler;
            }
        }
        list($className, $method) = $handler;
        if (Request::type() == Request::HTTP) {
            if (defined("$className::MIDDLEWARE") && $className::MIDDLEWARE) {
                foreach ($className::MIDDLEWARE as $midClass => $expected) {
                    if (!class_exists($midClass)) {
                        Response::error("Middleware $midClass no found", 500);
                        return;
                    }
                    if (!is_subclass_of($midClass, 'Core\Common\Base\Middleware')) {
                        Response::error("Middleware $midClass did not implements Middleware interface", 500);
                        return;
                    }
                    $ret = $midClass::run();
                    if ($expected !== $ret) {
                        Response::error($midClass::error(), $midClass::errorCode(), $midClass);
                        return;
                    }
                }
            }
        }
        $className::$method(...$arr);
    }

    /**
     * 注册路由中间件
     * @param $obj
     */
    public function setRouteMiddlewareObj($obj)
    {
        if (empty($this->routerMiddleware)) {
            $this->routerMiddleware = $obj;
        }
    }

    /**
     * 根据别名获取路由中间件
     * @param $alias
     * @return array
     */
    public function getRouteMiddleware($alias)
    {
        if (is_array($this->routerMiddleware)) {
            return isset($this->routerMiddleware[$alias]) ? $this->routerMiddleware[$alias] : null;
        }
        if (class_exists('\Swoole\Table') && $this->routerMiddleware instanceof \Swoole\Table) {
            $arr = $this->routerMiddleware->get($alias);
            if ($arr) {
                return $arr['middleware'];
            }
        }
        return null;
    }

    /**
     * @param $aliasMiddlewareList
     * @return array
     */
    public function convertRouterMiddlewareList($aliasMiddlewareList)
    {
        $middlewareList = [];
        foreach ($aliasMiddlewareList as $aliasMiddleware) {
            if ($middleware = $this->getRouteMiddleware($aliasMiddleware)) {
                $middlewareList[] = $middleware;
            }
        }
        return $middlewareList;
    }

}
